/*
 * MathObject.java - ScriptME
 * 
 * Copyright (c) 2009 Cesar Henriques <cesar at alttab.com.ar>.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the GNU Lesser Public License v2.1
 * which accompanies this distribution, and is available at
 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
 * 
 * Based on FESI Project
 * 
 * Contributors:
 * 	Jean-Marc Lugrin - initial API and implementation
 * 	Cesar Henriques <cesar at alttab.com.ar> - J2ME Porting and Extensions
 */
package org.scriptme.ecmascript.data;

import java.util.Random;
import org.scriptme.ecmascript.exceptions.EcmaScriptException;
import org.scriptme.ecmascript.exceptions.ProgrammingError;
import org.scriptme.ecmascript.interpreter.Evaluator;

/**
 * Implemements the EcmaScript Math singleton.
 */
public class MathObject extends ObjectPrototype {

	/** The fp. */
	private FunctionPrototype fp;

	/**
	 * Instantiates a new math object.
	 * 
	 * @param prototype
	 *            the prototype
	 * @param evaluator
	 *            the evaluator
	 * @param functionPrototype
	 *            the function prototype
	 * 
	 * @throws EcmaScriptException
	 *             the ecma script exception
	 */
	private MathObject(ESObject prototype, Evaluator evaluator,
			FunctionPrototype functionPrototype) throws EcmaScriptException {
		super(prototype, evaluator);

		// Initialization used to be in makeMathObject, but this caused
		// some problemsto the users of JBuilder. So it is moved in
		// the constructor

		putHiddenProperty("E", new ESNumber(Math.E));
		// putHiddenProperty("LN10", new ESNumber(Math.log(10.0d)));
		// putHiddenProperty("LN2", new ESNumber(Math.log(2.0d)));
		// putHiddenProperty("LOG2E", new ESNumber(1.0d/Math.log(2.0d)));
		// putHiddenProperty("LOG10E", new ESNumber(1.0d/Math.log(10.0d)));
		putHiddenProperty("PI", new ESNumber(Math.PI));
		putHiddenProperty("SQRT1_2", new ESNumber(1.0d / Math.sqrt(2.0d)));
		putHiddenProperty("SQRT2", new ESNumber(Math.sqrt(2.0d)));

		putHiddenProperty("abs", new BuiltinMathFunctionOne("abs", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.abs(arg);
			}
		});
		putHiddenProperty("acos", new BuiltinMathFunctionOne("acos", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.acos(arg);
			}
		});
		putHiddenProperty("asin", new BuiltinMathFunctionOne("asin", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.asin(arg);
			}
		});
		putHiddenProperty("atan", new BuiltinMathFunctionOne("atan", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.atan(arg);
			}
		});
		putHiddenProperty("atan2", new BuiltinMathFunctionTwo("atan2",
				evaluator, functionPrototype) {
			public double applyMathFunction(double arg1, double arg2) {
				return -1; // Math.atan2(arg1,arg2);
			}
		});
		putHiddenProperty("ceil", new BuiltinMathFunctionOne("ceil", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.ceil(arg);
			}
		});
		putHiddenProperty("cos", new BuiltinMathFunctionOne("cos", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.cos(arg);
			}
		});
		putHiddenProperty("exp", new BuiltinMathFunctionOne("exp", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.exp(arg);
			}
		});
		putHiddenProperty("floor", new BuiltinMathFunctionOne("floor",
				evaluator, functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.floor(arg);
			}
		});
		putHiddenProperty("log", new BuiltinMathFunctionOne("log", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.log(arg);
			}
		});
		putHiddenProperty("max", new BuiltinMathFunctionTwo("max", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg1, double arg2) {
				return Math.max(arg1, arg2);
			}
		});
		putHiddenProperty("min", new BuiltinMathFunctionTwo("min", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg1, double arg2) {
				return Math.min(arg1, arg2);
			}
		});
		putHiddenProperty("pow", new BuiltinMathFunctionTwo("pow", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg1, double arg2) {
				double d = Double.NaN;
				try {
					d = -1; // Math.pow(arg1,arg2);
				} catch (ArithmeticException e) {
					// return NaN
				}
				return d;
			}
		});
		putHiddenProperty("random", new BuiltinMathFunctionZero("random",
				evaluator, functionPrototype) {
			public double applyMathFunction() {
				return new Random().nextDouble();
			}
		});
		putHiddenProperty("round", new BuiltinMathFunctionOne("round",
				evaluator, functionPrototype) {
			public double applyMathFunction(double arg) {
				return -1; // Math.round(arg);
			}
		});
		putHiddenProperty("sin", new BuiltinMathFunctionOne("sin", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.sin(arg);
			}
		});
		putHiddenProperty("sqrt", new BuiltinMathFunctionOne("sqrt", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.sqrt(arg);
			}
		});
		putHiddenProperty("tan", new BuiltinMathFunctionOne("tan", evaluator,
				functionPrototype) {
			public double applyMathFunction(double arg) {
				return Math.tan(arg);
			}
		});
	}

	// overrides
	/*
	 * (non-Javadoc)
	 * 
	 * @see org.scriptme.ecmascript.data.ESObject#getESClassName()
	 */
	public String getESClassName() {
		return "Math";
	}

	// class of nilary functions
	/**
	 * The Class BuiltinMathFunctionZero.
	 */
	abstract class BuiltinMathFunctionZero extends BuiltinFunctionObject {

		/**
		 * Instantiates a new builtin math function zero.
		 * 
		 * @param name
		 *            the name
		 * @param evaluator
		 *            the evaluator
		 * @param fp
		 *            the fp
		 */
		BuiltinMathFunctionZero(String name, Evaluator evaluator,
				FunctionPrototype fp) {
			super(fp, evaluator, name, 0);
		}

		/**
		 * Apply math function.
		 * 
		 * @return the double
		 */
		abstract double applyMathFunction();

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.scriptme.ecmascript.data.FunctionPrototype#callFunction(org.scriptme.ecmascript.data.ESObject,
		 *      org.scriptme.ecmascript.data.ESValue[])
		 */
		public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
				throws EcmaScriptException {
			return new ESNumber(applyMathFunction());
		}
	}

	// class of unary functions
	/**
	 * The Class BuiltinMathFunctionOne.
	 */
	abstract class BuiltinMathFunctionOne extends BuiltinFunctionObject {

		/**
		 * Instantiates a new builtin math function one.
		 * 
		 * @param name
		 *            the name
		 * @param evaluator
		 *            the evaluator
		 * @param fp
		 *            the fp
		 */
		BuiltinMathFunctionOne(String name, Evaluator evaluator,
				FunctionPrototype fp) {
			super(fp, evaluator, name, 1);
		}

		/**
		 * Apply math function.
		 * 
		 * @param arg
		 *            the arg
		 * 
		 * @return the double
		 */
		abstract double applyMathFunction(double arg);

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.scriptme.ecmascript.data.FunctionPrototype#callFunction(org.scriptme.ecmascript.data.ESObject,
		 *      org.scriptme.ecmascript.data.ESValue[])
		 */
		public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
				throws EcmaScriptException {
			double arg = (arguments.length > 0) ? arguments[0].doubleValue()
					: Double.NaN;
			if (Double.isNaN(arg)) {
				return new ESNumber(Double.NaN);
			}
			return new ESNumber(applyMathFunction(arg));
		}
	}

	// class of dyadic functions
	/**
	 * The Class BuiltinMathFunctionTwo.
	 */
	abstract class BuiltinMathFunctionTwo extends BuiltinFunctionObject {

		/**
		 * Instantiates a new builtin math function two.
		 * 
		 * @param name
		 *            the name
		 * @param evaluator
		 *            the evaluator
		 * @param fp
		 *            the fp
		 */
		BuiltinMathFunctionTwo(String name, Evaluator evaluator,
				FunctionPrototype fp) {
			super(fp, evaluator, name, 2);
		}

		/**
		 * Apply math function.
		 * 
		 * @param arg1
		 *            the arg1
		 * @param arg2
		 *            the arg2
		 * 
		 * @return the double
		 */
		abstract double applyMathFunction(double arg1, double arg2);

		/*
		 * (non-Javadoc)
		 * 
		 * @see org.scriptme.ecmascript.data.FunctionPrototype#callFunction(org.scriptme.ecmascript.data.ESObject,
		 *      org.scriptme.ecmascript.data.ESValue[])
		 */
		public ESValue callFunction(ESObject thisObject, ESValue[] arguments)
				throws EcmaScriptException {
			if (arguments.length < 2) {
				throw new EcmaScriptException("Missing parameter in function "
						+ this);
			}
			double arg1 = (arguments.length > 0) ? arguments[0].doubleValue()
					: Double.NaN;
			double arg2 = (arguments.length > 01) ? arguments[1].doubleValue()
					: Double.NaN;
			if (Double.isNaN(arg1) || Double.isNaN(arg2)) {
				return new ESNumber(Double.NaN);
			}
			return new ESNumber(applyMathFunction(arg1, arg2));
		}
	}

	/**
	 * Utility function to create the Math single object.
	 * 
	 * @param evaluator
	 *            the Evaluator
	 * @param functionPrototype
	 *            The Function prototype attached to the evaluator
	 * @param prototype
	 *            the prototype
	 * 
	 * @return the Math singleton
	 */
	static public ESObject makeMathObject(Evaluator evaluator,
			ObjectPrototype prototype, FunctionPrototype functionPrototype) {
		try {
			MathObject mo = new MathObject(prototype, evaluator,
					functionPrototype);
			return mo;
		} catch (EcmaScriptException e) {
			e.printStackTrace();
			throw new ProgrammingError(e.getMessage());
		}
	}
}
